package com.hframe.http.client;

import com.hframe.thread.ThreadPool;
import org.apache.log4j.Logger;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpCookie;
import java.net.HttpURLConnection;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by heqian on 12/4/16.
 */

public class HttpClients implements Runnable {

    public enum Method {
        GET,
        HEAD,
        POST,
        PUT,
        DELETE,
        TRACE,
        OPTIONS
    }

    private static Logger log = Logger.getLogger(HttpClients.class);
    private URI uri = null;
    private boolean async = false;                       //异步执行
    private int timeout = 30 * 1000;                    //超时时长
    private HttpError error = null;                          //错误回调地址
    private boolean cache = false;                       //是否缓存
    private Progress downProgress = null;
    private Progress updateProgress = null;
    private Method method = Method.GET;
    private Success success = null;
    private HttpParam param = null;
    private HttpCookies cookies = null;
    private Map<String, String> property = new HashMap<>();
    private boolean isAutoRedirects = false;
    private boolean isOutLog = true;

    public boolean isOutLog() {
        return isOutLog;
    }

    public void setOutLog(boolean outLog) {
        isOutLog = outLog;
    }

    public void setCookies(HttpCookies cookies) {
        this.cookies = cookies;
    }

    public boolean isAutoRedirects() {
        return isAutoRedirects;
    }

    public void setAutoRedirects(boolean autoRedirects) {
        isAutoRedirects = autoRedirects;
    }

    public HttpClients() {
        property.put("Content-type", "application/x-www-from-urlencoded");
    }

    public URI getUri() {
        return uri;
    }

    public boolean isAsync() {
        return async;
    }

    public void setAsync(boolean async) {
        this.async = async;
    }

    public int getTimeout() {
        return timeout;
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    public HttpError getError() {
        return error;
    }

    public void setError(HttpError error) {
        this.error = error;
    }

    public boolean isCache() {
        return cache;
    }

    public void setCache(boolean cache) {
        this.cache = cache;
    }

    public Progress getDownProgress() {
        return downProgress;
    }

    public void setDownProgress(Progress downProgress) {
        this.downProgress = downProgress;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    public void setRequestProperty(String key, String value) {
        property.put(key, value);
    }

    public String getRequestProperty(String key) {
        return property.get(key);
    }

    public Success getSuccess() {
        return success;
    }

    public void setSuccess(Success success) {
        this.success = success;
        success.setHttpClient(this);
    }

    public void http() {
        if (cache) {
            ThreadPool.get().execute(this);
        } else {
            run();
        }
    }

    public URI getGetUrl(String url) {
        try {
            if (Method.GET == this.method && null != param && param instanceof KeyParam) {
                String params = param.getUrlString();
                if (0 < params.length()) {
                    int index = url.indexOf("?");
                    if (-1 == index)
                        return new URI(url + "?" + param.getUrlString());

                    if (url.length() - 1 == index)
                        return new URI(url + param.getUrlString());

                    index = url.indexOf("&");
                    if (url.length() - 1 == index)
                        return new URI(url + param.getUrlString());

                    return new URI(url + "&" + param.getUrlString());
                }
            }
            return new URI(url);
        } catch (Exception e) {
            log.error(e, e);
            return null;
        }
    }

    /**
     * 创建SSLContext对象，并使用我们指定的信任管理器初始化
     *
     * @return
     */
    protected SSLSocketFactory getSSLSocketFactory() throws Exception {
        TrustManager[] tm = {new HttpsTrustManager()};
        SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
        sslContext.init(null, tm, new java.security.SecureRandom());
        return sslContext.getSocketFactory();
    }

    @Override
    public void run() {
        HttpURLConnection conn = null;

        try {
            conn = (HttpURLConnection) uri.toURL().openConnection(); //使用URL打开一个链接

            if (conn instanceof HttpsURLConnection)
                ((HttpsURLConnection) conn).setSSLSocketFactory(getSSLSocketFactory());

            conn.setReadTimeout(timeout);
            conn.setDoInput(true); //允许输入流，即允许下载
            if (method.POST == this.method || method.DELETE == this.method)
                conn.setDoOutput(true); //允许输出流，即允许上传

            conn.setUseCaches(this.cache); //不使用缓冲
            conn.setRequestMethod(this.method.name());
            conn.setInstanceFollowRedirects(isAutoRedirects);

            String cookie = null;
            if (null != cookies && null != (cookie = cookies.getCookie(uri)))
                conn.setRequestProperty("Cookie", cookie);

            for (Map.Entry<String, String> item : property.entrySet()) {
                if (null != item.getValue())
                    conn.setRequestProperty(item.getKey(), item.getValue());
            }

            if (null != param) {
                param.init(conn);
                if (method.POST == this.method || method.DELETE == this.method)
                    conn.setRequestProperty("Content-Length", String.valueOf(param.getDataLenght()));
            }

            conn.connect();
            updateSrteam(conn);

            if (conn.getResponseCode() != 200 && null != error) {
                error.onError(conn.getResponseCode(), conn);
                return;
            }

            if (null != cookies)
                cookies.put(uri, conn.getHeaderFields());

            downloadSrteam(conn);

        } catch (Exception e) {
            if (isOutLog)
                log.error(e.getMessage(), e);
            if (null != error)
                error.onError(-1, conn);
        } finally {
            if (null != conn)
                conn.disconnect();
        }
    }

    public HttpCookie getCooke(String key) {
        if (null == cookies)
            return null;

        List<HttpCookie> cookie = cookies.getCookieStore().get(uri);
        for (HttpCookie item : cookie) {
            if (item.getName().equals(key))
                return item;
        }
        return null;
    }

    public List<HttpCookie> getCookes() {
        if (null == cookies)
            return null;
        return cookies.getCookieStore().get(uri);
    }

    protected void downloadSrteam(HttpURLConnection connection) throws Exception {
        InputStream input = null;
        try {
            if (null != success)
                success.start(connection);

            long contentLength = connection.getContentLength();
            input = connection.getInputStream();
            Progress.DataItem data = new Progress.DataItem();
            data.data = new byte[4096];
            long progress = 0;

            while ((data.length = input.read(data.data)) > 0) {
                if (null != downProgress)
                    if (!downProgress.onProgress(data, progress, contentLength))
                        return;

                if (null != success)
                    if (!success.onDownload(data, connection))
                        return;
                data = new Progress.DataItem();
                data.data = new byte[4096];
                progress += data.length;
            }

            if (null != success)
                success.finish(connection);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            if (null != input)
                input.close();
        }
    }


    protected boolean updateSrteam(HttpURLConnection connection) throws Exception {
        if (!(Method.POST == this.method || Method.DELETE == this.method))
            return false;

        OutputStream output = null;
        try {
            output = connection.getOutputStream();
            if (null != param && 0 < param.getDataLenght()) {
                param.read(connection, new HttpParam.Write() {
                    OutputStream output = null;
                    long progress = 0;

                    HttpParam.Write setOutput(OutputStream output) {
                        this.output = output;
                        return this;
                    }

                    @Override
                    public long write(byte[] buffer, int offset, int count) throws Exception {
                        if (null != updateProgress) {
                            Progress.DataItem data = new Progress.DataItem();
                            data.data = buffer;
                            data.offset = offset;
                            data.length = count;
                            if (!updateProgress.onProgress(data, progress, param.getDataLenght()))
                                return progress;
                        }
                        if (null != output)
                            output.write(buffer, offset, count);
                        return progress;
                    }
                }.setOutput(output));
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            if (null != output)
                output.close();
        }
        return true;
    }

    public void get(String url) {
        this.method = Method.GET;
        uri = getGetUrl(url);
        if (this.async)
            ThreadPool.get().execute(this);
        else
            run();
    }

    public void get(String url, KeyParam param) {
        this.method = Method.GET;
        this.param = param;
        uri = getGetUrl(url);
        if (this.async)
            ThreadPool.get().execute(this);
        else
            run();
    }

    public void post(String url) {
        this.method = Method.POST;
        uri = getGetUrl(url);
        if (this.async)
            ThreadPool.get().execute(this);
        else
            run();
    }

    public void post(String url, HttpParam param) {
        this.method = Method.POST;
        uri = getGetUrl(url);
        this.param = param;
        if (this.async)
            ThreadPool.get().execute(this);
        else
            run();
    }

    public void put(String url) {
        this.method = Method.PUT;
        uri = getGetUrl(url);
        if (this.async)
            ThreadPool.get().execute(this);
        else
            run();
    }

    public void put(String url, HttpParam param) {
        this.method = Method.PUT;
        uri = getGetUrl(url);
        this.param = param;
        if (this.async)
            ThreadPool.get().execute(this);
        else
            run();
    }


    public void delete(String url) {
        this.method = Method.DELETE;
        uri = getGetUrl(url);
        if (this.async)
            ThreadPool.get().execute(this);
        else
            run();
    }

    public void delete(String url, HttpParam param) {
        this.method = Method.DELETE;
        uri = getGetUrl(url);
        this.param = param;
        if (this.async)
            ThreadPool.get().execute(this);
        else
            run();
    }

    public void options(String url) {
        this.method = Method.OPTIONS;
        uri = getGetUrl(url);
        if (this.async)
            ThreadPool.get().execute(this);
        else
            run();
    }

    public void head(String url) {
        this.method = Method.HEAD;
        uri = getGetUrl(url);
        if (this.async)
            ThreadPool.get().execute(this);
        else
            run();
    }

}
